package cn.cas.mango.service;
import cn.cas.mango.config.Mapping;
import cn.cas.mango.dbscan.Data;
import cn.cas.mango.dto.param.GlobalParam;
import cn.cas.mango.dto.param.HitNews;
import cn.cas.mango.dto.param.HotKeywords;
import cn.cas.mango.dto.param.News;
import cn.cas.mango.dto.param.NewsCluster;
import cn.cas.mango.dto.param.neo4j.NodeEntity;
import cn.cas.mango.util.CheckParam;
import cn.cas.mango.util.GetArea;
import cn.cas.mango.util.Pair;
import cn.cas.mango.util.TimeUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch.core.DeleteByQueryRequest;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;


import java.io.InputStreamReader;
import java.io.StringReader;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Service
@Slf4j
public class PrintJsonCluster {
  private final ObjectMapper objectMapper = new ObjectMapper();
  @Autowired
  private GetJson getJson;

  @Autowired
  private PrintJson printJson;
  @Autowired
  private RequestService requestService;
  private String levelFile = "level.txt";

  private String[] blackListPath = {"normal.txt", "media.txt"};

  @Autowired
  private ElasticsearchClient client;

  @Value("${elasticsearch.newIndex}")
  private String INDEX;

  @Value("${elasticsearch.clusterIndex}")
  private String CLUSTERINDEX;

  @Value("${elasticsearch.clusterKeywords.mode}")
  private String clusterMode;

  public List<NewsCluster> getAll(String type) {

    SearchRequest.Builder builder1 = new SearchRequest.Builder().index(CLUSTERINDEX).query(q->q.term(
        t->t.field("type").value(type)));
    SearchRequest.Builder builder2 = new SearchRequest.Builder().index(CLUSTERINDEX).query(q->q.term(
        t->t.field("type").value(type)));
    return requestService.getLarge(builder1, builder2, NewsCluster.class);
  }

  private HitNews wrapHitNews(NewsCluster newsCluster) {
    HitNews hitNews = new HitNews(newsCluster.getScore());
    hitNews.setNm(newsCluster.getId());
    hitNews.setEntities(newsCluster.getEntities());
    hitNews.setEntitiesPerson(newsCluster.getEntitiesPerson());
    hitNews.setEntitiesPlace(newsCluster.getEntitiesPlace());
    hitNews.setEntitiesOrganization(newsCluster.getEntitiesOrganization());
    hitNews.setKeywords(newsCluster.getKeywords());
    hitNews.setFssj(newsCluster.getDate());
    hitNews.setSjmc(newsCluster.getTitle());
    return hitNews;
  }

  private void addSelf(Map<String, Map<String, Object>> clusters) {
    for (String key: clusters.keySet()) {
      Map<String, Object> stringObjectMap = clusters.get(key);
      List<HitNews> like = (List<HitNews>) stringObjectMap.get("like");
      HitNews hitNews = (HitNews) stringObjectMap.get("self");
      like.add(hitNews);
    }
  }

  private void delete() {
    try {
      DeleteByQueryRequest deleteByQueryRequest = DeleteByQueryRequest.of(d->d.index(CLUSTERINDEX).query(q->q.bool(b->b.mustNot(m->m.term(t->t.field("type").value("0"))))));
      log.info("delete query {}", deleteByQueryRequest);
      client.deleteByQuery(deleteByQueryRequest);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  private Map<String, List<String>> getKey2Keywords(List<News> newsList) {
    Map<String, List<String>> ret = new HashMap<>();
    for (News news: newsList) {
      List<String> filteredKeywords = news.getFilteredKeywords(printJson.getBlackListSet());
      String id = news.getNm();
      ret.put(id, filteredKeywords);
    }
    return ret;
  }

  public JSONObject main(GlobalParam globalParam) {
    CheckParam.checkGlobal(globalParam);
    delete();
    log.info("delete not-0 type");
    JSONObject ret = new JSONObject();
    int i = 0;
    List<NewsCluster> newsClusters = null;
    Map<Integer, Map<String, List<String>>> map2Original = new LinkedHashMap<>();
    if (clusterMode.equals("original")) {
      List<List<News>> allNews2;
      try {

        allNews2 = getJson.main(globalParam);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
      List<News> allNews = new ArrayList<>();
      allNews2.forEach(x->allNews.addAll(x));
      map2Original.put(-1, getKey2Keywords(allNews));
    }
    try (BufferedReader reader = new BufferedReader(new FileReader("level.txt"))) {
      String line;
      int count = 0;
      while ((line = reader.readLine()) != null) {
        double simParam = Double.parseDouble(line);
//        log.info("process level {}", i+1);
        {
          List<NewsCluster> all;
          if (newsClusters == null) {
            all = getAll(i+"");
          }
            else
            all = newsClusters;
          map2Original.put(i, new HashMap<>());
          int finalI = i;
          all.forEach(x->map2Original.get(finalI).put(x.getId(), (List<String>)x.getChild()));
          List<HitNews> collect = removeDuplicates(all.stream()
              .map(this::wrapHitNews).collect(Collectors.toList()));
          TimeUtil timeUtil = new TimeUtil();
          SameNewsService sameNewsService3 = new SameNewsService(collect, SortOrder.Desc, "time",
              globalParam.getPageSize(), globalParam.getAddScore(), ++count, map2Original);
          Map<String, Map<String, Object>> clusters3 = sameNewsService3.getSameNewsList(simParam,
              globalParam.getV(), globalParam.getMaxClusterSize(), 2);
          log.info("cost {}s", timeUtil.getCost());
          addSelf(clusters3);
          newsClusters = wrapCluster(clusters3, (i+1)+"");
          List<BulkOperation> clusterOperations = newsClusters.stream()
              .map(news ->
                  BulkOperation.of(b -> b
                      .index(y -> {
                            try {
                              return y
                                  .index(CLUSTERINDEX)
                                  .document(JsonData.fromJson(objectMapper.writeValueAsString(news)));
                            } catch (JsonProcessingException e) {
                              throw new RuntimeException(e);
                            }
                          }
                      )
                  )).collect(Collectors.toList());

          requestService.bulkWrite(clusterOperations);

          i++;
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    }

    return ret;
  }



  private List<HitNews> removeDuplicates(List<HitNews> hitNewsList) {
    Map<String, HitNews> hitNewsMap = new HashMap<String, HitNews>(){{
      for (HitNews hitNews: hitNewsList) {
        put(hitNews.getNm(), hitNews);
      }
    }};
    return new ArrayList<>(hitNewsMap.values());
  }


  private Map<String, Map<String, Double>> getClusterKeywords(Map<String, Map<String, Object>> clusters) {
    Map<String, Map<String, Double>> ret = new HashMap<>();
    for (String clusterId: clusters.keySet()) {
      Map<String, Object> stringObjectMap = clusters.get(clusterId);
      Map<String, Integer> clusterKeywords = (Map<String, Integer>) stringObjectMap.get("cluster_keywords");
      Map<String, Double> newClusterKeywords = new LinkedHashMap<>();
      if (!clusterKeywords.isEmpty()) {
        double total = clusterKeywords.values().stream().reduce((a, b) -> a + b).get();
        for (String key: clusterKeywords.keySet()) {
          newClusterKeywords.put(key, clusterKeywords.get(key) / total);
        }
      }
      ret.put(clusterId, newClusterKeywords);
    }
    return ret;
  }

  private Map<String, Set<String>> getClusterEnt(Map<String, Map<String, Object>> clusters, String type) {
    Map<String, Set<String>> ret = new HashMap<>();
    for (String clusterId: clusters.keySet()) {
      Map<String, Object> stringObjectMap = clusters.get(clusterId);
      Set<String> clusterEnt = (Set<String>) stringObjectMap.get(type);
      ret.put(clusterId, clusterEnt);
    }
    return ret;
  }

  private List<NewsCluster> wrapCluster(Map<String, Map<String, Object>> clusters3, String type) {
    Map<String, Map<String, Double>> clusterKeywords = getClusterKeywords(clusters3);
    Map<String, Set<String>> clusterEntitiesPerson = getClusterEnt(clusters3, "cluster_entities_person");
    Map<String, Set<String>> clusterEntitiesPlace = getClusterEnt(clusters3, "cluster_entities_place");
    Map<String, Set<String>> clusterEntitiesOrganization = getClusterEnt(clusters3, "cluster_entities_organization");
    Map<String, Set<String>> clusterEntities = getClusterEnt(clusters3, "cluster_entities");
    List<NewsCluster> ret = new ArrayList<>();
    for (String key: clusterKeywords.keySet()) {
      Map<String, Double> keywords = clusterKeywords.get(key);
      Set<String> entitiesPerson = clusterEntitiesPerson.get(key);
      Set<String> entitiesPlace = clusterEntitiesPlace.get(key);
      Set<String> entitiesOrganization = clusterEntitiesOrganization.get(key);
      Set<String> entities = clusterEntities.get(key);

      List<HitNews> like = (List<HitNews>) clusters3.get(key).get("like");
      ret.add(new NewsCluster(1.0, key, type, wrapMap(keywords), entitiesPerson, entitiesPlace,
          entitiesOrganization, entities, "2020-01-01",
          like.stream().map(News::getNm).collect(Collectors.toList()), ((HitNews)clusters3.get(key).get("self")).getSjmc()));
    }
    return ret;
  }

  private List<Map<String, Object>> wrapMap(Map<String, Double> clustersKeywords) {
    List<Map<String, Object>> ret = new ArrayList<>();
    for (String key: clustersKeywords.keySet()) {
      Double score = clustersKeywords.get(key);
      ret.add(new HashMap<String, Object>(){{
        put("name", key);
        put("score", score);
      }});
    }
    return ret;
  }
}
